#ifndef INTERDATA_H
#define INTERDATA_H

#define ID_INT 0
#define ID_BOOL 1
#define ID_FLOAT 2
#define ID_STRING 3
#define ID_INTARR 4
#define ID_BOOLARR 5
#define ID_FLOATARR 6
#define ID_STRINGARR 7
#define ID_SIMPLEIMAGE 8

#define IV_OUTOFBOUNDS -1
#define IV_NONE 0
#define IV_INT 1
#define IV_FLOAT 2
#define IV_STRING 3
#define IV_UCHAR 4

#include <string>
#include <vector>
#include <map>

class IDataCore
{
public:
	IDataCore();
	~IDataCore();
	IDataCore* duplicate();
	void delPos(int p);
	int pushNone();

	int _refCt;

	std::vector<int> _typeList;
	std::vector<void*> _valList;
	std::map<std::string, int> _nameMap;
};

template <class T>
T interpretIDAs(IDataCore* core, int pos, int t1, const T& defVal)
{
	if(core == NULL)
		return defVal;
	if(pos < 0 || (unsigned int)pos >= core->_typeList.size())
		return defVal;
	void* pt = core->_valList[pos];
	int t2 = core->_typeList[pos];
	if(pt == NULL || t1 != t2)
		return defVal;
	else
		return *((T*)(pt));
}

template <class T>
void setIDAs(IDataCore* core, int pos, int t, const T& val)
{
	if(core == NULL || pos < 0)
		return;
	if((unsigned int)pos >= core->_valList.size())
	{
		core->_valList.resize(pos + 1, NULL);
		core->_typeList.resize(pos + 1, IV_NONE);
	}
	core->delPos(pos);
	core->_valList[pos] = new T(val);
	core->_typeList[pos] = t;
}

template <class T>
void pushIDAs(IDataCore* core, int t, const T& val)
{
	if(core == NULL)
		return;
	int np = core->pushNone();
	setIDAs<T>(core, np, t, val);
}

class IData
{
public:
	IData();
	IData(int* dummy); // this exists so you can do IData(NULL)
	IData(const IData& rhs);
	~IData();

	IData deepcopy();
	IData& operator=(const IData& rhs);

	int				geti(int pos);
	float			getf(int pos);
	std::string		gets(int pos);
	unsigned char	getu(int pos);

	// warning! getname is slow (must iterate through entire map)
	std::string		getname(int pos);

	void			seti(int pos, int val);
	void			setf(int pos, float val);
	void			sets(int pos, const std::string& val);
	void			setu(int pos, unsigned char val);
	void			setname(int pos, const std::string& name);

	void			pushi(int val);
	void			pushf(float val);
	void			pushs(const std::string& val);
	void			pushu(unsigned char val);

	int				getType(int pos);
	int				size();

	// clears the value, type, and name (sets type to 0)
	void			clear(int pos);

	// note, if named is called with a nonexistent name, it will return the
	// first empty slot as well as give that slot the name
	// (so bla.seti(bla.named("foo"), 0, 3) will work even if "foo" does not exist)
	int named(const std::string& name);
	
	// this is basically just meant for serializers
	IDataCore* getRawData();
protected:
	//void*			getRawPos(int pos1, int pos2);
	IDataCore*		_data;
};

class InterData
{
public:
	// static function for releasing an InterData
	// (right now just deletes it)
	static void release(InterData* rhs);
	static InterData* allocate();
	static InterData* allocate(int numvals);
	static InterData* allocate(InterData* rhs);

	// constructors
	InterData();
	InterData(int numvals);

	void setDeletable(bool val);
	bool isDeletable();

	// the exact type of the data (there are only
	// going to be about two of these)
	int getExactType();

	// number of values
	int numValues();

	// function for querying the data
	int isType(int slot, int t);
	int canonicalType(int slot);

	// convenience function for checking if the data could
	// be interpreted in a specific way (simpler than making
	// a million isBlank calls)
	// sig can have the following characters:
	// i: int        I: int array
	// b: bool       B: bool array
	// f: float      F: float array
	// s: string     S: string array
	//
	// e.g., hasSignature("iifB") checks if it has
	// two ints followed by a float and then a byte
	// array
	int hasSignature(const char* sig);

	// functions for getting data
	int getInt(int slot);
	unsigned char getBool(int slot);
	double getFloat(int slot);
	std::string getString(int slot);

	std::vector<int>& getIntArray(int slot);
	std::vector<unsigned char>& getBoolArray(int slot);
	std::vector<double>& getFloatArray(int slot);
	std::vector<std::string>& getStringArray(int slot);

	// functions for pushing data (specific to InterData)
	void pushInt(int val);
	void pushBool(unsigned char val);
	void pushFloat(double val);
	void pushString(const std::string& val);

	// Note that these do not copy the array, but just push it
	// directly, so you should make new arrays to push
	void pushIntArray(std::vector<int>* aval);
	void pushBoolArray(std::vector<unsigned char>* aval);
	void pushFloatArray(std::vector<double>* aval);
	void pushStringArray(std::vector<std::string>* aval);

	// functions for setting data
	// These are necessary instead of just returning references with the
	// getters because other interdata types may not be able to return references
	// to their internals...
	void setInt(int slot, int val);
	void setBool(int slot, unsigned char val);
	void setFloat(int slot, double val);
	void setString(int slot, std::string val);

	// functions for serialization
	// return number of bytes read or written
	int writeToByteArray(unsigned char* dest);
	int readFromByteArray(unsigned char* src);

	// clears out all data
	void clear();

	// concatenates the right interdata to this one; the
	// right interdata is left empty.
	void concat(InterData* rhs);

	// destructor
	~InterData();

private:
	// Internally, InterData keeps a vector of void pointers
	// to its values as well as an array of types
	std::vector<int> _typeList;
	std::vector<void*> _valList;
	bool _deletable;
};

#endif